using System;
using System.Collections;
using System.Collections.Generic;

namespace Elephant.Framework
{
    public class EntityComponentCollection : IEntityComponentCollection
    {
        Dictionary<Type, EntityComponent> components = new Dictionary<Type, EntityComponent>();

        bool isReadOnly;

        #region IEntityComponentCollection Members

        /// <summary>
        /// Raised when a component has been added.
        /// </summary>
        public event EventHandler<EntityComponentEventArgs> ComponentAdded;
        /// <summary>
        /// Raised when a component has been added. Override this method to handle when a component has been added.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnComponentAdded(EntityComponentEventArgs e)
        {
            if (ComponentAdded != null) {
                ComponentAdded(this, e);
            }
        }

        /// <summary>
        /// Raised when a component has been removed.
        /// </summary>
        public event EventHandler<EntityComponentRemovedEventArgs> ComponentRemoved;
        /// <summary>
        /// Raised when a component has been removed. Override this method to handle when a component has been removed.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnComponentRemoved(EntityComponentRemovedEventArgs e)
        {
            if (ComponentRemoved != null) {
                ComponentRemoved(this, e);
            }

            // looping through to avoid removing a dependency (it'll just get added again)
            EntityComponent[] tmpComponents = new EntityComponent[components.Values.Count];
            components.Values.CopyTo(tmpComponents, 0);

            for (int i = 0; i < tmpComponents.Length; i++) {
                tmpComponents[i].Initialize(e.UpdateDependencies);
            }
        }

        public void Add(EntityComponent item, bool overwrite)
        {
            if (IsReadOnly) {
                throw new ArgumentException("The entity is read-only.");
            }
            /*
            // hack: to avoid using Entity.Add(new Component()), but this proves troublesome as you're allowed to pass an array of components for entity creation
            if (item.Owner == null) {
                throw new ArgumentException("Cannot add an owner-less component.");
            }
            */
            if (components.ContainsKey(item.GetType())) {
                if (overwrite) {
                    // remove the existing component, and remove it immediately to be able to execute the following
                    // without troubles
                    RemoveImmediate(components[item.GetType()], true);
                } else {
                    return;
                }
            }

            // if we just removed the component, but it's still here it means another component  
            // was dependant on it and it had to be attached again
            if (!components.ContainsKey(item.GetType())) {
                components.Add(item.GetType(), item);

                // fire this event now, in order to be able to assign an Owner before initializing
                // note: the factory will actually do this for us, but we can't use it in all situations so this is a hacky solution
                OnComponentAdded(new EntityComponentEventArgs(item));

                // xml composition:
                // if camera is dependent on a transform - a transform will be added, and camera will have a reference to THAT exact component
                // now, if a transform is added after camera in the xml doc, the transform will be removed from camera entity
                // and new transform is added - now, camera will still have the reference to the old transform - which is not in entity though
                item.Initialize(true);

                if (item is IDrawableContent) {
                    IDrawableContent drawableContent = item as IDrawableContent;
                    drawableContent.LoadGraphicsContent(true);
                }
            }
        }

        /// <summary>
        /// Finds the first occurence of a component of type T.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T Find<T>() where T : EntityComponent
        {
            EntityComponent[] tmpComponents = new EntityComponent[components.Values.Count];
            components.Values.CopyTo(tmpComponents, 0);

            for (int i = 0; i < tmpComponents.Length; i++) {
                if (tmpComponents[i] is T) {
                    return (T)tmpComponents[i];
                }
            }

            // todo: exception
            //throw new Exception("Well, the component wasn't found.");
            return default(T);
        }

        /// <summary>
        /// Finds all occurences of components of type T.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public List<T> FindAll<T>() where T : EntityComponent
        {
            List<T> matches = new List<T>();

            // to avoid garbage, we create an array we can 'for' over
            EntityComponent[] tmpComponents = new EntityComponent[components.Values.Count];
            components.Values.CopyTo(tmpComponents, 0);

            for (int i = 0; i < tmpComponents.Length; i++) {
                if (tmpComponents[i] is T) {
                    matches.Add((T)tmpComponents[i]);
                }
            }

            return matches;
        }

        /// <summary>
        /// Determines whether the collection contains a component of type T.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public bool Contains<T>() where T : EntityComponent
        {
            // insufficient implementation, imagine following scenario:
            //
            // entity has:
            //      LookAtCamera (derived from Camera)
            //      Transform
            //
            // now, contains will result in:
            // entity.Contains<EntityComponent>() == false
            //
            // this is due to components being stored with their Type as Key, looping is necessary to achieve wanted results

            //return components.ContainsKey(typeof(T));

            // to avoid garbage, we create an array we can 'for' over
            EntityComponent[] tmpComponents = new EntityComponent[components.Values.Count];
            components.Values.CopyTo(tmpComponents, 0);

            for (int i = 0; i < tmpComponents.Length; i++) {
                if (tmpComponents[i] is T) {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Removes the first occurence of a component of type T.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public bool Remove<T>() where T : EntityComponent
        {
            EntityComponent[] tmpComponents = new EntityComponent[components.Values.Count];
            components.Values.CopyTo(tmpComponents, 0);

            for (int i = 0; i < tmpComponents.Length; i++) {
                if (tmpComponents[i] is T) {
                    return tmpComponents[i].Owner.Manager.Remove(tmpComponents[i]);
                }
            }

            return false;
        }

        /// <summary>
        /// Removes the first occurence of a component of type T, immediately. It is advised to use Remove for common usage.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="updateDependencies"></param>
        /// <returns></returns>
        public bool RemoveImmediate<T>(bool updateDependencies) where T : EntityComponent
        {
            EntityComponent[] tmpComponents = new EntityComponent[components.Values.Count];
            components.Values.CopyTo(tmpComponents, 0);

            for (int i = 0; i < tmpComponents.Length; i++) {
                if (tmpComponents[i] is T) {
                    return RemoveImmediate(tmpComponents[i], updateDependencies);
                }
            }

            return false;
        }

        /// <summary>
        /// Removes the specified component, immediately. It is advised to use Remove for common usage.
        /// </summary>
        /// <param name="component"></param>
        /// <param name="updateDependencies"></param>
        /// <returns></returns>
        public bool RemoveImmediate(EntityComponent component, bool updateDependencies)
        {
            Type t = component.GetType();

            if (components.ContainsKey(t)) {
                // dispose
                component.Dispose();
                // remove it immediately
                bool result = components.Remove(t);
                // but queue it up to be removed from maintained lists aswell
                //component.Owner.Manager.Remove(component);
                component.Owner.Manager.RemoveImmediate(component, updateDependencies);

                OnComponentRemoved(new EntityComponentRemovedEventArgs(component, updateDependencies));

                return result;
            }

            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key">The type of component to get.</param>
        /// <returns></returns>
        public EntityComponent this[Type key]
        {
            get 
            { 
                // todo: this probably wont work as expected.. wont get LookAtCamera if trying to get Camera type
                // hmm well.. it's a better solution it seems
                return components[key];
            }
            /*set
            {
                // experimental, not sure if this is worth having (used in xml template stuff)
                components[key] = value;
            }*/
        }

        #endregion

        #region ICollection<EntityComponent> Members

        public void Add(EntityComponent item)
        {
            Add(item, false);
        }

        public void Clear()
        {
            // this can be troublesome, consider:
            // if we remove a component that another component is dependant on, it'll just get added again
            // this means we could miss components.. 
            // solutions:
            // hack: solution could be recursively calling clear until there's no components left :D
            // solved: removals now specify whether or not to update dependencies (every component will still
            // be initialized, but will only check/add dependencies if told)
            EntityComponent[] tmpComponents = new EntityComponent[components.Values.Count];
            components.Values.CopyTo(tmpComponents, 0);

            for (int i = 0; i < tmpComponents.Length; i++) {
                RemoveImmediate(tmpComponents[i], false);
            }
        }

        /// <summary>
        /// Determines whether the collection contains the specified component.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Contains(EntityComponent item)
        {
            return components.ContainsKey(item.GetType());
        }

        public void CopyTo(EntityComponent[] array, int arrayIndex)
        {
            components.Values.CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get 
            {
                return components.Count; 
            }
        }

        public bool IsReadOnly
        {
            get 
            {
                return isReadOnly;
            }
            set
            {
                if (isReadOnly != value) {
                    isReadOnly = value;
                }
            }
        }

        /// <summary>
        /// Removes the specified component.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Remove(EntityComponent item)
        {
            if (components.ContainsKey(item.GetType())) {
                // queue up component for removal
                return item.Owner.Manager.Remove(item);
            }

            return false;
        }

        #endregion

        #region IEnumerable<EntityComponent> Members

        public IEnumerator<EntityComponent> GetEnumerator()
        {
            return components.Values.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return components.GetEnumerator();
        }

        #endregion
    }
}
